//
// Project werewolf: api.cpp
// Created by alfielin on 2021/11/29.
//

#pragma once

#include "util/sha1.h"
#include "repo.cpp"
#include "constants.cpp"
#include "util/sha1.cpp"
#include <set>
#include <mutex>

namespace nz {
namespace picit {
namespace api {

static void SetData(const HttpContextPtr &ctx, std::initializer_list<std::pair<std::string, nlohmann::json>> kvs) {
    nlohmann::json data;
    for (const auto &pp: kvs) {
        data[pp.first] = pp.second;
    }
    ctx->response->json[DATA] = data;
}

static std::string LatestBasename(const std::string &vpath) {
    return HPath::filename(vpath) +
           "_latest_" + SHA1()(vpath) +
           "." + HPath::suffixname(vpath);;
}

static std::pair<bool, std::string>
SaveFile(const std::string &data, const std::string &vpath, const std::string &sha1, int version) {
    std::string abspath = HPath::join(UPLOAD_FILE_ROOT, HPath::filename(vpath)) +
                          "_v" + std::to_string(version) +
                          "_" + sha1 +
                          "." + HPath::suffixname(vpath);
    std::string latest_basename = LatestBasename(vpath);
    std::string abslatest = HPath::join(UPLOAD_FILE_ROOT, latest_basename);
    std::string virlatest = HPath::join(UPLOAD_FILE_VIRTUAL_ROOT, latest_basename);
    LOGGER->info("Start saving file {} @ {}", vpath, abspath);
    HFile file;
    auto &repo = PicitRepository::get();
    if (repo.save_file(vpath, abspath, version, sha1) == -1) {
        return { false, vpath };
    }
    if (file.open(abspath.c_str(), "wb") != 0) {
        return { false, vpath };
    }
    file.write(data.c_str(), data.size());
    if (HPath::exists(abslatest.c_str())) {
        remove(abslatest.c_str());
    }
    link(abspath.c_str(), abslatest.c_str());
    LOGGER->info("Saved file {} @ {}", vpath, abspath);
    return { true, virlatest };
}

int hello_world(const HttpContextPtr &ctx) {
    LOGGER->error("Hello world");
    LOGGER->info("{}:{}", ctx->request->client_addr.ip.c_str(), ctx->request->client_addr.port);
    LOGGER->info("Request {}", ctx->request->Dump(true, true).c_str());
    SetData(ctx, {{ "int",     1 },
                  { "double",  3.14 },
                  { "bools",   { true,    false }},
                  { "strings", { "Hello", "World", "!" }}});
    throw std::runtime_error("Good night");
    return HTTP_STATUS_OK;
}

static int _upload_internal(const HttpContextPtr &ctx);

int upload(const HttpContextPtr &ctx) {
    // this is a wrapper to prevent repetitive submission
    static std::set<int> serial_nums;
    static std::mutex serial_mutex;
    if (!ctx->is(MULTIPART_FORM_DATA)) {
        ctx->response->json[CODE] = -HTTP_STATUS_BAD_REQUEST;
        ctx->response->json[MSG] = "Content type should be multipart form data.";
        return HTTP_STATUS_BAD_REQUEST;
    }
    int serial = std::stoi(ctx->get(UPLOAD_FILE_SERIAL_KEY));
    NZ_BEGIN_CRITICAL(serial_mutex)
        if (!serial_nums.count(serial)) {
            serial_nums.insert(serial);
        } else {
            ctx->response->json[CODE] = -HTTP_STATUS_BAD_REQUEST;
            ctx->response->json[MSG] = "Repetitive submission.";
            return HTTP_STATUS_BAD_REQUEST;
        }
    NZ_END_CRITICAL
    int code = _upload_internal(ctx);
    serial_nums.erase(serial);
    return code;
}

static int _upload_internal(const HttpContextPtr &ctx) {
    std::vector<std::string> failidx;
    std::vector<std::string> urls;
    auto &repo = PicitRepository::get();
    int cnt = stoi(ctx->form(UPLOAD_FILE_COUNT_KEY));
    for (int i = 0; i < cnt; ++i) {
        std::string key = UPLOAD_FILE_IDX_PREFIX + std::to_string(i);
        const auto &formdata = ctx->request->form[key];
        if (formdata.content.empty()) {
            failidx.push_back(key);
            urls.emplace_back("Invalid data");
            continue;
        }
        const auto &vpath = formdata.filename;
        std::string sha1 = SHA1()(vpath + formdata.content);
        auto entry = repo.get_by_sha1(sha1);
        if (entry) { // the file exists
            if (repo.is_latest(entry())) {
                urls.push_back(HPath::join(UPLOAD_FILE_VIRTUAL_ROOT, LatestBasename(vpath)));
            } else {
                urls.push_back(HPath::join(UPLOAD_FILE_VIRTUAL_ROOT, HPath::basename(entry().abspath)));
            }
        } else {
            std::pair<bool, std::string> res;
            entry = repo.get_by_vpath(vpath);
            if (entry) { // has file is updated
                res = SaveFile(formdata.content, vpath, sha1, 1 + entry().version);
            } else { // new file
                res = SaveFile(formdata.content, vpath, sha1, 0);
            }
            if (!res.first) { // if fail to save
                failidx.push_back(key);
            }
            urls.push_back(res.second);
        }
    }
    SetData(ctx, {
            { "failedIdx", failidx },
            { "urls",      urls }
    });
    return HTTP_STATUS_OK;
}


}
}
}

